﻿using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using SLS.ExClassLib.MultiThreading.Extensions;

namespace SLS.ExClassLib.MultiThreading.DataStructures.Tree
{
    [Serializable]
    public sealed class TreeNode<T> : IDisposable
    {
        private bool _isDisposed;
        private TreeNode<T> _parent;

        public TreeNode()
        {
            Parent = null;
            Children = new TreeNodeList<T>(this);
        }

        public TreeNode(T value)
        {
            Value = value;
            Children = new TreeNodeList<T>(this);
        }

        public TreeNode(TreeNode<T> parent)
        {
            if (parent.IsNull())
                throw new ArgumentException("parent");
            Parent = parent;
            Children = new TreeNodeList<T>(this);
        }

        public TreeNode(TreeNodeList<T> children)
        {
            if (children.IsNull())
                throw new ArgumentException("children");
            Parent = null;
            Children = children;
            children.Parent = this;
        }

        public TreeNode(TreeNode<T> parent, TreeNodeList<T> children)
        {
            if (parent.IsNull())
                throw new ArgumentException("parent");

            if (children.IsNull())
                throw new ArgumentException("children");

            Parent = parent;
            Children = children;
            children.Parent = this;
        }

        public IEnumerable<TreeNode<T>> DepthFirstEnumerator
        {
            get { return GetDepthFirstIterator(this); }
        }

        public bool IsRoot
        {
            get { return Parent == null; }
        }

        public int Count
        {
            get
            {
                int i = IsRoot ? 0 : 1;
                foreach (var child in Children)
                {
                    i += child.Count;
                }
                return i;
            }
        }

        [XmlIgnore]
        public TreeNode<T> Parent
        {
            get { return _parent; }
            set
            {
                if (value == _parent)
                {
                    return;
                }

                if (_parent != null)
                {
                    _parent.Children.Remove(this);
                }

                if (value != null && !value.Children.Contains(this))
                {
                    value.Children.Add(this);
                }

                _parent = value;
            }
        }

        public TreeNode<T> Root
        {
            get
            {
                TreeNode<T> node = this;
                while (node.Parent != null)
                {
                    node = node.Parent;
                }
                return node;
            }
        }

        public TreeNodeList<T> Children { get; private set; }

        public T Value { get; set; }

        /// <summary>
        /// Reports a depth of nesting in the tree, starting at 0 for the root.
        /// </summary>
        public int Depth
        {
            get
            {
                int depth = 0;
                TreeNode<T> node = this;
                while (node.Parent != null)
                {
                    node = node.Parent;
                    depth++;
                }
                return depth;
            }
        }

        private IEnumerable<TreeNode<T>> GetDepthFirstIterator(TreeNode<T> root)
        {
            yield return root;
            foreach (var child in root.Children)
            {
                foreach (var node in GetDepthFirstIterator(child))
                {
                    yield return node;
                }
            }
        }

        public override string ToString()
        {
            string Description = "[" + (Value == null ? "<null>" : Value.ToString()) + "] ";

            Description += "Depth=" + Depth + ", Children=" + Children.Count;
            if (Root == this)
            {
                Description += " (Root)";
            }
            return Description;
        }

        #region IDisposable

        public bool IsDisposed
        {
            get { return _isDisposed; }
        }

        public void Dispose()
        {
            CheckDisposed();

            foreach (var node in Children)
            {
                node.Dispose();
            }

            _isDisposed = true;
        }

        private void CheckDisposed()
        {
            if (IsDisposed)
            {
                throw new ObjectDisposedException(GetType().Name);
            }
        }

        #endregion
    }
}